﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Threading;
using System.Drawing;
using System.Drawing.Imaging;
using Meshomatic;

using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;

namespace Reconstructor
{
    class MeshDrawer
    {

        private bool readyToDraw;
        public MeshDrawer(MeshData meshData, uint texture)
        {
            m = meshData;
            tex = texture;
        }

        #region Funky Stuff
        MeshData m;
        uint dataBuffer;
        uint indexBuffer;
        uint tex;
        int vertOffset, normOffset, texcoordOffset;
        OpenTK.Vector3d up = new OpenTK.Vector3d(0.0, 1.0, 0.0);
        OpenTK.Vector3d viewDirection = new OpenTK.Vector3d(1.0, 1.0, 1.0);
        double viewDist = 1.0;

        string vShaderSource = @"
void main() {
	gl_Position = ftransform();
	gl_TexCoord[0] = gl_MultiTexCoord0;
}
";
        string fShaderSource = @"
uniform sampler2D tex;
void main() {
	gl_FragColor = texture2D(tex, gl_TexCoord[0].st);
}
"; 
        #endregion

        private int CompileShaders()
        {
            int programHandle, vHandle, fHandle;
            vHandle = GL.CreateShader(ShaderType.VertexShader);
            fHandle = GL.CreateShader(ShaderType.FragmentShader);
            GL.ShaderSource(vHandle, vShaderSource);
            GL.ShaderSource(fHandle, fShaderSource);
            GL.CompileShader(vHandle);
            GL.CompileShader(fHandle);
            Console.Write(GL.GetShaderInfoLog(vHandle));
            Console.Write(GL.GetShaderInfoLog(fHandle));

            programHandle = GL.CreateProgram();
            GL.AttachShader(programHandle, vHandle);
            GL.AttachShader(programHandle, fHandle);
            GL.LinkProgram(programHandle);
            Console.Write(GL.GetProgramInfoLog(programHandle));
            return programHandle;
        }

        private void LoadBuffers(MeshData m)
        {
            float[] verts, norms, texcoords;
            uint[] indices;
            m.OpenGLArrays(out verts, out norms, out texcoords, out indices);
            GL.GenBuffers(1, out dataBuffer);
            GL.GenBuffers(1, out indexBuffer);

            // Set up data for VBO.
            // We're going to use one VBO for all geometry, and stick it in 
            // in (VVVVNNNNCCCC) order.  Non interleaved.
            int buffersize = (verts.Length + norms.Length + texcoords.Length);
            float[] bufferdata = new float[buffersize];
            vertOffset = 0;
            normOffset = verts.Length;
            texcoordOffset = (verts.Length + norms.Length);

            verts.CopyTo(bufferdata, vertOffset);
            norms.CopyTo(bufferdata, normOffset);
            texcoords.CopyTo(bufferdata, texcoordOffset);

            bool v = false;
            for (int i = texcoordOffset; i < bufferdata.Length; i++)
            {
                if (v)
                {
                    bufferdata[i] = 1 - bufferdata[i];
                    v = false;
                }
                else
                {
                    v = true;
                }
            }

            // Load geometry data
            GL.BindBuffer(BufferTarget.ArrayBuffer, dataBuffer);
            GL.BufferData<float>(BufferTarget.ArrayBuffer, (IntPtr)(buffersize * sizeof(float)), bufferdata,
                          BufferUsageHint.StaticDraw);

            // Load index data
            GL.BindBuffer(BufferTarget.ElementArrayBuffer, indexBuffer);
            GL.BufferData<uint>(BufferTarget.ElementArrayBuffer,
                          (IntPtr)(indices.Length * sizeof(uint)), indices, BufferUsageHint.StaticDraw);
        }

        public void DrawBuffer()
        {
            // Push current Array Buffer state so we can restore it later
            GL.PushClientAttrib(ClientAttribMask.ClientVertexArrayBit);

          //  GL.ClientActiveTexture(TextureUnit.Texture0);
            GL.BindTexture(TextureTarget.Texture2D, tex);

            GL.BindBuffer(BufferTarget.ArrayBuffer, dataBuffer);
            // Normal buffer
            GL.NormalPointer(NormalPointerType.Float, 0, (IntPtr)(normOffset * sizeof(float)));

            // TexCoord buffer
            GL.TexCoordPointer(2, TexCoordPointerType.Float, 0, (IntPtr)(texcoordOffset * sizeof(float)));

            // Vertex buffer
            GL.VertexPointer(3, VertexPointerType.Float, 0, (IntPtr)(vertOffset * sizeof(float)));

            // Index array
            GL.BindBuffer(BufferTarget.ElementArrayBuffer, indexBuffer);
            GL.DrawElements(BeginMode.Triangles, m.Tris.Length * 3, DrawElementsType.UnsignedInt, IntPtr.Zero);

            // Restore the state
            GL.PopClientAttrib();
        }

       public void Init(EventArgs e)
        {
            
                // We do some heuristics to try to auto-zoom to a reasonable distance.  And it generally works!
                double w, l, h;
                double maxdim;
                m.Dimensions(out w, out l, out h);
                Console.WriteLine("Model dimensions: {0} x {0} x {0} (theoretically)", w, l, h);
                maxdim = Math.Max(Math.Max(w, l), h);
                viewDist = (float)(maxdim * 2);

                GL.UseProgram(CompileShaders());
                LoadBuffers(m);
        }

    }
}
